/**
 * Copyright [2019] [LiBo/Alex of copyright liboware@gmail.com ]
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.hyts.inject;

import com.hyts.inject.container.BeanContainer;
import com.hyts.inject.error.InjectError;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import lombok.var;

import javax.inject.Inject;
import javax.inject.Qualifier;
import javax.inject.Singleton;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

import static java.util.Objects.isNull;

/**
 * @project-name:coffice-toolbox
 * @package-name:com.hyts.inject
 * @author:LiBo/Alex
 * @create-date:2020-04-10 16:31
 * @copyright:libo-alex4java
 * @email:liboware@gmail.com
 * @description:
 */
public class Injector {

    /**
     * 对象bean容器对象
     */
    @Getter
    @Setter
    private BeanContainer container;

    /**
     * 构造器
     * @param isSingleton
     */
    public Injector(boolean isSingleton){
        container = isNull(container)?new BeanContainer():
                isSingleton?container:new BeanContainer();
    }

    /**
     * 构造器
     */
    public Injector(){
       this(true);
    }


    /**
     * 注册单例对象
     * @param clazz 依赖注入的类
     * @param o 注入到的实际寄生类对象
     * @param <T>
     * @return
     */
    public <T> Injector registerSingleton(Class<T> clazz, T o) {
        // 插入失败则说明已经存在着重复的单例对象数据值
        if (Objects.nonNull(container.getSingletons().put(clazz, o))) {
            throw new InjectError("duplicated singleton object for the same class " + clazz.getCanonicalName());
        }
        return this;
    }

    /**
     * 注册类别单例对象(名称类别)
     * @param clazz
     * @param anno
     * @param o
     * @param <T>
     * @return
     */
    public <T> Injector registerQualified(Class<T> clazz, Annotation anno, T o) {
        // 先判断是否属于Qualifier注解类
        if (!anno.annotationType().isAnnotationPresent(Qualifier.class)) {
            throw new InjectError(
                    "annotation must be decorated with Qualifier " + anno.annotationType().getCanonicalName());
        }
        //如果存在则获取该类的对象注入集合,先判断当前的类是否已经被扫描进入容器里面
        var os = container.getQualifieds().get(clazz);
        //没有扫描到容器 则创建对象进入里面进行扫描
        if (isNull(os)) {
            os = new ConcurrentHashMap<>();
            container.getQualifieds().put(clazz, os);
        }
        // 如果存储为空则出现存储重复键值问题
        if (isNull(os.put(anno, o))) {
            throw new InjectError(
                    String.format("duplicated qualified object with the same qualifier %s with the class %s",
                            anno.annotationType().getCanonicalName(), clazz.getCanonicalName()));
        }
        return this;
    }

    /**
     * z注入单例对象类
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> Injector registerSingletonClass(Class<T> clazz) {
        return this.registerSingletonClass(clazz, clazz);
    }

    /**
     * 注入单例对象类
     * @param parentType
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> Injector registerSingletonClass(Class<?> parentType, Class<T> clazz) {
        // 是否包含在缓存池
        if (Objects.nonNull(container.getSingletonClasses().put(parentType, clazz))) {
            throw new InjectError("duplicated singleton class " + parentType.getCanonicalName());
        }
        return this;
    }

    /**
     *  注入单例对象类 (类别标识)
     * @param parentType
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> Injector registerQualifiedClass(Class<?> parentType, Class<T> clazz) {
        for (Annotation anno : clazz.getAnnotations()) {
            if (anno.annotationType().isAnnotationPresent(Qualifier.class)) {
                return this.registerQualifiedClass(parentType, anno, clazz);
            }
        }
        throw new InjectError("class should decorated with annotation tagged by Qualifier");
    }

    /**
     * 注入单例对象类 (类别标识)
     * @param parentType
     * @param anno
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> Injector registerQualifiedClass(Class<?> parentType, Annotation anno, Class<T> clazz) {
        if (!anno.annotationType().isAnnotationPresent(Qualifier.class)) {
            throw new InjectError(
                    "annotation must be decorated with Qualifier " + anno.annotationType().getCanonicalName());
        }
        var annos = container.getQualifiedClasses().get(parentType);
        // 操作服务注册实例对象
        if (isNull(annos)) {
            annos = new ConcurrentHashMap<>();
            container.getQualifiedClasses().put(parentType, annos);
        }
        // 操作服务存放对象
        if (annos.put(anno, clazz) != null) {
            throw new InjectError(String.format("duplicated qualifier %s with the same class %s",
                    anno.annotationType().getCanonicalName(), parentType.getCanonicalName()));
        }
        return this;
    }

    /**
     * 创建新对象
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T createNew(Class<T> clazz) {
        return this.createNew(clazz, null);
    }

    /**
     * 创建新对象
     * @param clazz
     * @param consumer
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> T createNew(Class<T> clazz, Consumer<T> consumer) {
        var o = container.getSingletons().get(clazz);
        if (Objects.nonNull(o)) {
            return (T) o;
        }
        var cons = new ArrayList<Constructor<T>>();
        T target = null;
        for (var con : clazz.getDeclaredConstructors()) {
            // 默认构造期不需要Inject注解createFromQualified
            if (!con.isAnnotationPresent(Inject.class) && con.getParameterCount() > 0) {
                continue;
            }
            if (!con.isAccessible()) {
                con.setAccessible(true);
            }
            cons.add((Constructor<T>) con);
        }
//        if (cons.size() > 1) {
//            throw new InjectError("dupcated constructor for injection class " + clazz.getCanonicalName());
//        }
        if (cons.size() == 0) {
            throw new InjectError("no accessible constructor for injection class " + clazz.getCanonicalName());
        }
        // 放入表示未完成的容器
        container.getReadyClasses().add(clazz);
        try {
            Constructor constructor = clazz.getConstructor();
            constructor.setAccessible(true);
            container.getReadyClassesObject().put(clazz,constructor.newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 构造器注入
        target = createFromConstructor(cons.get(0));
        // 从未完成的容器取出
        container.getReadyClasses().remove(clazz);
        container.getReadyClassesObject().remove(clazz);

        var isSingleton = clazz.isAnnotationPresent(Singleton.class);
        if (!isSingleton) {
            isSingleton = getContainer().getSingletonClasses().containsKey(clazz);
        }
        if (isSingleton) {
            getContainer().getSingletons().put(clazz, target);
        }
        if (consumer != null) {
            consumer.accept(target);
        }
        injectMembers(target);
        return target;
    }

    private <T> T createFromConstructor(Constructor<T> con) {
        var params = new Object[con.getParameterCount()];
        var i = 0;
        for (Parameter parameter : con.getParameters()) {
            if(container.getReadyClasses().contains(parameter.getType()) ){
                if(container.getReadyClassesObject().containsKey(parameter.getType())){
                    return (T) container.getReadyClassesObject().get(parameter.getType());
                }
                throw new InjectError(String.format("circular dependency on constructor , the root class is %s",con.getDeclaringClass().getCanonicalName()));
            }
            var param = createFromParameter(parameter);
            if (param == null) {
                throw new InjectError(String.format("parameter should not be empty with name %s of class %s",
                        parameter.getName(), con.getDeclaringClass().getCanonicalName()));
            }
            params[i++] = param;
        }
        try {
            return con.newInstance(params);
        } catch (Exception e) {
            throw new InjectError("create instance from constructor error", e);
        }
    }

    @SuppressWarnings("unchecked")
    private <T> T createFromParameter(Parameter parameter) {
        var clazz = parameter.getType();
        T t = createFromQualified(parameter.getDeclaringExecutable().getDeclaringClass(), clazz,
                parameter.getAnnotations());
        if (t != null) {
            return t;
        }
        return (T) createNew(clazz);
    }

    @SuppressWarnings("unchecked")
    private <T> T createFromField(Field field) {
        var clazz = field.getType();
        T t = createFromQualified(field.getDeclaringClass(), field.getType(), field.getAnnotations());
        if (t != null) {
            return t;
        }
        return (T) createNew(clazz);
    }

    @SuppressWarnings("unchecked")
    private <T> T createFromQualified(Class<?> declaringClazz, Class<?> clazz, Annotation[] annos) {
        var qs = container.getQualifieds().get(clazz);
        if (qs != null) {
            Set<Object> os = new HashSet<>();
            for (var anno : annos) {
                var o = qs.get(anno);
                if (o != null) {
                    os.add(o);
                }
            }
            if (os.size() > 1) {
                throw new InjectError(String.format("duplicated qualified object for field %s@%s",
                        clazz.getCanonicalName(), declaringClazz.getCanonicalName()));
            }
            if (!os.isEmpty()) {
                return (T) (os.iterator().next());
            }
        }
        var qz = container.getQualifiedClasses().get(clazz);
        if (qz != null) {
            Set<Class<?>> oz = new HashSet<>();
            Annotation annoz = null;
            for (var anno : annos) {
                var z = qz.get(anno);
                if (z != null) {
                    oz.add(z);
                    annoz = anno;
                }
            }
            if (oz.size() > 1) {
                throw new InjectError(String.format("duplicated qualified classes for field %s@%s",
                        clazz.getCanonicalName(), declaringClazz.getCanonicalName()));
            }
            if (!oz.isEmpty()) {
                final var annozRead = annoz;
                var t = (T) createNew(oz.iterator().next(), (o) -> {
                    this.registerQualified((Class<T>) clazz, annozRead, (T) o);
                });
                return t;
            }
        }
        return null;
    }

    /**
     * 注入成员
     * @param t
     */
    public <T> void injectMembers(T t) {
        List<Field> fields = new ArrayList<>();
        for (Field field : t.getClass().getDeclaredFields()) {
            if(!field.isAccessible()){
                field.setAccessible(true);
            }
            if (field.isAnnotationPresent(Inject.class)) {
                fields.add(field);
            }
        }
        for (Field field : fields) {
            Object f = createFromField(field);
            try {
                field.set(t, f);
            } catch (Exception e) {
                throw new InjectError(
                        String.format("set field for %s@%s error", t.getClass().getCanonicalName(), field.getName()),
                        e);
            }
        }
    }
    /**
     * 获取对象
     * @param clazz
     * @return
     */
    public <T> T getInstance(Class<T> clazz) {
        return createNew(clazz);
    }

}
